<template>
	<div>
		<div id="webgl-content" class="webgl-content"></div>
		<div class="buttons">
			<button @click="playModel">播放下一个</button>
			<button @click="$router.back()" style="margin-left: 5px;">返回模型展示</button>
		</div>
		<div id="loading" v-if="isLoading">
			<div>
				<div>...loading...</div>
				<div class="progress"><div class="progressbar" :style="`width: ${progress}%`"></div></div>
			</div>
		</div>
	</div>
</template>

<script>
// 方式 1: 导入整个 three.js核心库
import * as THREE from 'three';
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
// import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';
import ResourceTracker from '@/utils/ResourceTracker.js';
export default {
	name: 'Demo1',
	data() {
		return {
			isLoading: true,
			progress: 50,
			loadManager: null,
			resMgr: null,
			track: null,

			camera: null,
			scene: null,
			renderer: null,
			controls: null,
			mixer: null,
			clock: null,
			sceneRoot: null, //所有新对象的父对象
			modelList: [
				{ name: 'Samba Dancing', url: 'static/models/fbx/Samba Dancing.fbx' }, //
				{ name: 'Bull', url: 'static/models/fbx/Animals/Models/Bull_01.fbx' },
				{ name: 'BrownBear', url: 'static/models/fbx/Animals/Models/brownBear.fbx' },				
				{ name: 'PolarBear', url: 'static/models/fbx/Animals/Models/polarBear.fbx' },
				{ name: 'Cow', url: 'static/models/fbx/Animals/Models/Cow_00.fbx' },
				{ name: 'Rooster', url: 'static/models/fbx/Animals/Models/Rooster.fbx' },
				{ name: 'Raven', url: 'static/models/fbx/Animals/Models/Raven_01.fbx' },
				{ name: 'Deagon', url: 'static/models/fbx/Animals/Models/Dragon_01.fbx' },
				{ name: 'Aurochs', url: 'static/models/fbx/Animals/Models/Aurochs_00.fbx' }
			],
			index: 0,
			loaderModel: null
		};
	},
	created() {
		console.info(['THREE.REVISION -- ', THREE.REVISION]);
		this.resMgr = new ResourceTracker();
		this.track = this.resMgr.track.bind(this.resMgr);

		this.sceneRoot = new THREE.Object3D();
	},
	mounted() {
		window.addEventListener('resize', this.onWindowResize);
		this.init();

		// setInterval(()=>{
		// 	this.playModel(1)
		// },2000)
	},
	beforeDestroy() {
		// 移除绑定的事件监听
		window.removeEventListener('resize', this.onWindowResize);
		try {
			this.resMgr.dispose();
			this.scene.clear();
			this.renderer.dispose();
			this.renderer.forceContextLoss();
			this.renderer.content = null;
		} catch (e) {
			console.log(e);
		}
	},
	methods: {
		async init() {
			this.clock = new THREE.Clock();
			this.initLoadManager();
			this.loaderModel = new FBXLoader(this.loadManager);
			// 渲染器
			this.initRenderer();
			// 场景
			this.initScene();
			// 像机
			this.initCamera();
			// 灯光
			this.initLight();
			// 控制
			this.initControls();

			// 模型加载
			await this.loadModelList();
			this.playModel();

			// 渲染
			this.render(); //放在最后
		},

		initLoadManager() {
			this.loadManager = new THREE.LoadingManager();
			// 加载开始回调
			this.loadManager.onStart = (url, itemsLoaded, itemsTotal) => {
				this.isLoading = true;
				//console.log('Started loading file: ' + url + '.\nLoaded ' + itemsLoaded + ' of ' + itemsTotal + ' item ');
			};
			// 加载完成回调
			this.loadManager.onLoad = () => {
				this.isLoading = false;
			};
			// 加载进度回调
			this.loadManager.onProgress = (urlOfLastItemLoaded, itemsLoaded, itemsTotal) => {
				this.progress = Math.round(((itemsLoaded / itemsTotal) * 100) | 0);
				//console.log(this.progress);
			};
			// 加载错误回调
			this.loadManager.onError = url => {
				console.error('There was an error loading ' + url);
			};
		},

		initRenderer() {
			// 渲染器
			const container = document.getElementById('webgl-content');
			const width = container.clientWidth;
			const height = container.clientHeight;
			this.renderer = new THREE.WebGLRenderer({ antialias: true });
			this.renderer.setSize(width, height);
			this.renderer.setPixelRatio(window.devicePixelRatio);
			// this.renderer.toneMapping = THREE.ACESFilmicToneMapping;
			// this.renderer.toneMappingExposure = 1;
			// this.renderer.outputEncoding = THREE.sRGBEncoding;
			this.renderer.shadowMap.enabled = true;			
			this.renderer.shadowMap.type = THREE.PCFSoftShadowMap; // default THREE.PCFShadowMap
			container.appendChild(this.renderer.domElement);
			// console.log("this.renderer.info:",this.renderer.info)
		},

		initScene() {
			// 场景
			this.scene = new THREE.Scene();
			// this.scene.fog = new THREE.Fog( 0xa0a0a0, 200, 1000 );
			const loader = new THREE.CubeTextureLoader(this.loadManager);
			const textureCube = loader.load([
				'static/textures/skyboxes/textures/computer-history-museum/pos-x.jpg',
				'static/textures/skyboxes/textures/computer-history-museum/neg-x.jpg',
				'static/textures/skyboxes/textures/computer-history-museum/pos-y.jpg',
				'static/textures/skyboxes/textures/computer-history-museum/neg-y.jpg',
				'static/textures/skyboxes/textures/computer-history-museum/pos-z.jpg',
				'static/textures/skyboxes/textures/computer-history-museum/neg-z.jpg'
			]);			
			this.scene.background = textureCube;

			// 辅助网格-可选
			const grid = new THREE.GridHelper(2000, 20, 0xffffff, 0xffffff);
			grid.material.opacity = 0.2;
			grid.material.depthWrite = false;
			grid.material.transparent = true;
			this.scene.add(grid);
			//坐标轴辅助-可选
			// const axesHelper = new THREE.AxesHelper(500);
			// this.scene.add(axesHelper);

			// 地面 ground
			let textureBg = new THREE.TextureLoader(this.loadManager).load('static/textures/Forest Floor.jpg');
			textureBg.wrapS = THREE.MirroredRepeatWrapping;
			textureBg.wrapT = THREE.MirroredRepeatWrapping;
			textureBg.repeat.x = 2;
			textureBg.repeat.y = 2;
			const material = new THREE.MeshStandardMaterial({ map: textureBg });
			const mesh = new THREE.Mesh(new THREE.PlaneGeometry(1000, 1000), material);
			mesh.rotation.x = -Math.PI / 2;
			mesh.receiveShadow = true;
			this.scene.add(mesh);
			
		
			this.scene.add(this.sceneRoot);
		},

		initCamera() {
			let size;
			size = this.renderer.getSize(new THREE.Vector2());
			this.camera = new THREE.PerspectiveCamera(45, size.width / size.height, 1, 10000);
			this.camera.position.set(-300, 200, 400);
		},

		initControls() {
			this.controls = null;
			this.controls = new OrbitControls(this.camera, this.renderer.domElement);
			// 如果使用animate方法时，将此函数删除
			//this.controls.addEventListener( 'change', this.renderer );
			// 使动画循环使用时阻尼或自转 意思是否有惯性
			this.controls.enableDamping = true;
			//动态阻尼系数 就是鼠标拖拽旋转灵敏度
			//controls.dampingFactor = 0.25;
			//是否可以缩放
			this.controls.enableZoom = true;
			//是否自动旋转
			// this.controls.autoRotate = true;
			//设置相机距离原点的最远距离
			// this.controls.minDistance = 2;
			//设置相机距离原点的最远距离
			// this.controls.maxDistance = 60;
			//是否开启右键拖拽
			this.controls.enablePan = true;
			this.controls.target.set(0, 100, 0);

			//更新控制器。必须在摄像机的变换发生任何手动改变后调用，
			//或如果.autoRotate或.enableDamping被设置时，在update循环里调用。
			this.controls.update();
		},

		initLight() {
			let lightGroup = new THREE.Group();
			lightGroup.name = 'LightGroup';
			let light;
			// 环境光		
			light = new THREE.HemisphereLight(0xffffff, 0x444444);
			light.position.set(0, 200, 0);
			lightGroup.add(light);

			// 方向光 类似太阳光
			light = new THREE.DirectionalLight(0xffffff, 0.8);
			light.position.set(100, 200, 300);
			light.castShadow = true;
			light.shadow.camera.top = 300;
			light.shadow.camera.bottom = -100;
			light.shadow.camera.left = -120;
			light.shadow.camera.right = 120;
			
			light.shadow.camera.near = 0.5; // default
			light.shadow.camera.far = 1000; // default
			
			lightGroup.add(light);
			// 阴影相机辅助
			// const helper = new THREE.CameraHelper( light.shadow.camera );
			// this.scene.add( helper );
			
			// 加在场景中
			this.scene.add(lightGroup);
		},
		
		onWindowResize() {
			//console.log('onWindowResize....');
			const container = document.getElementById('webgl-content');
			const width = container.clientWidth;
			const height = container.clientHeight;
			if (this.camera) {
				this.camera.aspect = width / height;
				this.camera.updateProjectionMatrix();
			}
			this.renderer && this.renderer.setSize(width, height);
			this.renderer && this.renderer.render(this.scene, this.camera);
		},
		
		// 不缩放模型大小，通过调整像机位置等属性，使得居中显示
		setCenterByCamera(theObject) {
			let root = theObject; //this.scene;
			let box = new THREE.Box3().setFromObject(root);
			let temp = new THREE.Vector3();
			let boxCenter = box.getCenter(new THREE.Vector3());
			let boxSize = box.getSize(new THREE.Vector3())		
			// console.log('old center,size:', boxCenter, boxSize);
			let maxAxis = Math.max(boxSize.x, boxSize.y, boxSize.z);

			const sizeToFitOnScreen = boxSize.length();	

			//(sizeToFitOnScreen, boxSize, boxCenter, camera) {
			const halfSizeToFitOnScreen = sizeToFitOnScreen * 0.5;
			const halfFovY = THREE.MathUtils.degToRad(this.camera.fov * 0.5);
			const distance = halfSizeToFitOnScreen / Math.tan(halfFovY);
			// compute a unit vector that points in the direction the camera is now
			// in the xz plane from the center of the box
			const direction = new THREE.Vector3()
				.subVectors(this.camera.position, boxCenter)
				.multiply(new THREE.Vector3(1, 0, 1))
				.normalize();

			// move the camera to a position distance units way from the center
			// in whatever direction the camera was from the center already
			this.camera.position.copy(direction.multiplyScalar(distance).add(boxCenter));

			// pick some near and far values for the frustum that
			// will contain the box.
			this.camera.near = sizeToFitOnScreen / 100;
			this.camera.far = sizeToFitOnScreen * 100;

			this.camera.updateProjectionMatrix();

			// point the camera to look at the center of the box
			this.camera.lookAt(boxCenter.x, boxCenter.y, boxCenter.z);
		},
		
		// 不改变相机位置等属性，通过缩放模型大小，改变位置，使得居中显示
		scaleToCenter(theObject) {
			let root = theObject; //this.scene;
			let box = new THREE.Box3().setFromObject(root);
			let center = box.getCenter(new THREE.Vector3());
			let size = box.getSize(new THREE.Vector3());
			// console.log('old center:', center);
			// console.log('old size:',  size);
			let maxAxis = Math.max(size.x, size.y, size.z);
			root.scale.multiplyScalar(5 / maxAxis); //缩放模型大小，可调整参数 5
			box.setFromObject(root);
			center = box.getCenter(new THREE.Vector3());
			size = box.getSize(new THREE.Vector3());			
			root.position.sub(center); // 调整对象在父对象（场景）中的位置，居中			
			// console.log('new center:', center);
			// console.log('new size:',  size);
		},
		
		// 显示播放模型
		playModel() {
			const model = this.modelList[this.index];
			const object = model.object;
			if (object) {
				this.sceneRoot.children.forEach(child => {
					this.sceneRoot.remove(child);
				});
				this.sceneRoot.add(object);

				// this.scaleToCenter(object);

				this.mixer = new THREE.AnimationMixer(object);
				const firstClip = object.animations[0];
				const action = this.mixer.clipAction(firstClip);
				action.play();
			}

			this.index += 1;
			if (this.index >= this.modelList.length) {
				this.index = 0;
			}
			if (this.index < 0) {
				this.index = 0;
			}
		},
		// 渲染动画
		render() {
			const delta = this.clock.getDelta();
			this.mixer && this.mixer.update(delta);
			this.controls && this.controls.update();
			this.renderer && this.camera && this.scene && this.resizeRendererToDisplaySize(this.renderer, this.camera, this.scene);
			requestAnimationFrame(this.render);
		},
		
		resizeRendererToDisplaySize(renderer, camera, scene) {
			const canvas = renderer.domElement;
			const width = canvas.clientWidth;
			const height = canvas.clientHeight;
			const needResize = canvas.width !== width || canvas.height !== height;
			if (needResize) {
				renderer.setSize(width, height, false);
				camera.aspect = width / height;
				camera.updateProjectionMatrix();
			}
			renderer && renderer.render(scene, camera);
		},
		// 加载列表中的所有 FBX 模型
		async loadModelList() {
			// Instantiate a loader
			this.resMgr.dispose(); //卸载内存
			for (let model of this.modelList) {
				const url = model.url; // 模型 URL 可以是本地静态资源，也可以是网上的 url
				const object = await this.loaderModel.loadAsync(url);

				// 跟踪对象内存使用
				this.track(object);
				object.name = model.name;
				// 投影
				object.traverse(child => {
					if (child.isMesh) {
						child.castShadow = true;
						child.receiveShadow = true;
					}
				});
				model.object = object;

				// 动画剪辑
				const animsByName = {};
				object.animations.forEach(clip => {
					animsByName[clip.name] = clip;
				});
				model.animations = animsByName;
			}
		}
	}
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.webgl-content * {
	border: 0;
	margin: 0;
	padding: 0;
}
.webgl-content {
	position: absolute;
	top: 50%;
	left: 50%;
	width: 100%;
	height: 100%;
	-webkit-transform: translate(-50%, -50%);
	transform: translate(-50%, -50%);
}

.buttons {
	position: absolute;
	margin: 10px;
	top: 0;
	right: 0;
	display: flex;
	flex-direction: row;
}

#loading {
	position: absolute;
	left: 0;
	top: 0;
	width: 100%;
	height: 100%;
	display: flex;
	align-items: center;
	justify-content: center;
	text-align: center;
	font-size: xx-large;
	font-family: sans-serif;
}
#loading > div > div {
	padding: 2px;
}
.progress {
	width: 50vw;
	border: 1px solid black;
}
.progressbar {
	width: 0%;
	transition: width ease-out 0.5s;
	height: 0.5em;
	background-color: #888;
	background-image: linear-gradient(
		-45deg,
		rgba(255, 255, 255, 0.5) 25%,
		transparent 25%,
		transparent 50%,
		rgba(255, 255, 255, 0.5) 50%,
		rgba(255, 255, 255, 0.5) 75%,
		transparent 75%,
		transparent
	);
	background-size: 50px 50px;
	animation: progressanim 2s linear infinite;
}
</style>
